/**
 * 
 */
package org.youisoft.summer.evaluator.function.clustering;

import java.util.List;

import org.youisoft.summer.evaluator.InvokeException;
import org.youisoft.summer.evaluator.context.CalContext;
import org.youisoft.summer.evaluator.function.AbstractFunction;
import org.youisoft.summer.evaluator.operator.Operator;
import org.youisoft.summer.evaluator.operator.base.ListOperator;
import org.youisoft.summer.evaluator.operator.base.NumberOperator;

/**
 * @author zijiang.jl
 *
 */
public abstract class AbstractKSeriesFunction extends AbstractFunction{
	
	
	protected int defaultKNumber(){
		return 2;
	}
	
	protected int defaultFactor(){
		return 1;
	}
	
	@Override
	protected boolean checkOperator(int index, Operator<?> operator) {
		if(index==0&&ListOperator.class.isAssignableFrom(operator.getClass())
				){
			return true;
		}else if(index==1&&NumberOperator.class.isAssignableFrom(operator.getClass())){
			return true;
		}else if(index==2&&NumberOperator.class.isAssignableFrom(operator.getClass())){
			return true;
		}
		return false;
	}



	@Override
	public <C,R,P> Operator<?> doInvoke(CalContext<C,R,P> context,Operator<?>... operator) throws InvokeException{
		int k=this.defaultKNumber();
		if(operator.length==2) k=NumberOperator.class.cast(operator[1]).value().intValue();
		if(k<this.defaultKNumber()) k=this.defaultKNumber();
		
		double factor=this.defaultFactor();
		if(operator.length==3) factor=NumberOperator.class.cast(operator[2]).value().doubleValue();
		
		
		@SuppressWarnings("unchecked")
		ListOperator<Operator<?>> lo=ListOperator.class.cast(operator[0]);
		if(lo.value().size()==1) return lo;
		
		java.util.List<Number> points=null;
		for(Object o:lo.value()){
			if(o instanceof NumberOperator){
				if(points==null) points=new java.util.ArrayList<Number>();
				points.add(NumberOperator.class.cast(o).value());
			}
		}
		
		if(points!=null){
			double[] center=new double[k];
			int[] centerIndex=new int[points.size()];

			this.initialKClustering(center, points, centerIndex);
			
			this.clustering(center, points, centerIndex);
			
			java.util.List<Operator<?>> lop=this.outlierClustering(context,center, points, centerIndex,factor);
			if(lop==null) return lo;
			return lo.setValue(lop);
		}
		
		return Operator.nullInstance;
	}
	
	protected void initialKClustering(double[] center,List<Number> points,int[] centerIndex){
		for(int i=0;i<center.length;i++){
			if(i>0&&center[i-1]==points.get(i).doubleValue()) continue;
			center[i]=points.get(i).doubleValue();
		}
	}
	
	private void clustering(double[] center,List<Number> points,int[] centerIndex){
		boolean continuation=false;
		for(int i=0;i<points.size();i++){
			double dist=points.get(i).doubleValue()-center[centerIndex[i]];
			dist=dist<0?-dist:dist;
			for(int j=0;j<center.length;j++){
				double di=points.get(i).doubleValue()-center[j];
				di=di<0?-di:di;
				if(dist>di){
					centerIndex[i]=j;
					continuation=true;
				}
			}
		}
		//====================
		this.adjustCenter(center, points, centerIndex);
		if(continuation)  this.clustering(center, points, centerIndex);	
	}
	
	protected abstract void adjustCenter(double[] center,List<Number> points,int[] centerIndex);

	protected <C,R,P> java.util.List<Operator<?>> outlierClustering(CalContext<C,R,P> context,double[] center,List<Number> points,int[] centerIndex,double factor) throws InvokeException{
		int[] cnt=new int[center.length];
		
		for(int i=0;i<centerIndex.length;i++){
			cnt[centerIndex[i]]++;
		}
		
		
		int min=cnt[0];
		int max=cnt[0];
		int minIdx=0;
		int maxIdx=0;
		for(int i=0;i<cnt.length;i++){
			if(cnt[i]<min){
				min=cnt[i];
				minIdx=i;
			}
			
			if(cnt[i]>max){
				max=cnt[i];
				maxIdx=i;
			} 
		}
		
	
		
		if((double)cnt[minIdx]/(double)cnt[maxIdx]<factor){
			java.util.List<Operator<?>> normalPoints=new java.util.ArrayList<Operator<?>>();
			for(int i=0;i<centerIndex.length;i++){
				if(centerIndex[i]!=minIdx){
					normalPoints.add(new NumberOperator(points.get(i)));
				}
			}
			
			return normalPoints;
		}else{
			return null;
		}
		
	}



	@Override
	protected int minOperatorCount() {
		return 1;
	}
	
	
	
	
}
